/* symtab.c
 *
 * Implements the symbol table
 *
 * A flat symbol table is the root of all evil.
 */

#include "symtab.h"
#include "gen.h"
#include "mem.h"



struct symbol_t* sym[MAX_SYMTAB_HASHKEYS];
int symtab_scope = -1;



/* ----------------------------------------------------------------
 * Returns a pointer to a new function_description
 * ----------------------------------------------------------------
 */
struct function_description_t *symtab_new_function_description()
{
	struct function_description_t *fd;

	fd = (struct function_description_t*) malloc (sizeof(struct function_description_t));
	CHECK_MEM_ERROR(fd)

		fd->fd = NULL;
	fd->basic_block_label = NULL;

	return fd;
}



/* ----------------------------------------------------------------
 * Returns a pointer to a new symtab
 * ----------------------------------------------------------------
 */
struct symbol_t *symtab_new()
{
	struct symbol_t *s;

	s = (struct symbol_t*) malloc (sizeof(struct symbol_t));
	CHECK_MEM_ERROR(s)
		s->lexeme = NULL;
	s->id = -1;
	s->scope = -1;
	s->class = NULL;
	s->seen_at_least_once = 0;
	s->line_number = -1;
	s->offset = -1;
	s->type = -1;
	s->next = NULL;

	return s;
}


/* ----------------------------------------------------------------
 * Returns a numeric id for a new symbol scope
 * ----------------------------------------------------------------
 */
int symtab_new_scope()
{
	symtab_scope++;

	if (symtab_scope < 0) {
		/* We rolled over; bail */
		error(-1, "Maximum number of internal scope ids exceeded");
	}

	return symtab_scope;
}



/* ----------------------------------------------------------------
 * Inserts lexeme into symbol table sym and returns the position
 * it was inserted in
 * ----------------------------------------------------------------
 */
struct symbol_t* symtab_insert(char* lexeme, char *class, int scope, int line_number, char * if_func_its_name)
{
	struct symbol_t *symbol;
	int key;

	symbol = symtab_new();
	symbol->lexeme = tolower(lexeme);

	/* Insert the new symbol in it's proper location */
	key = makekey(tolower(lexeme), MAX_SYMTAB_HASHKEYS);
	symbol->scope = scope;
	symbol->line_number = line_number;
	symbol->class = class;
	symbol->next = sym[key];
	if (if_func_its_name)
		symbol->func = if_func_its_name;
	sym[key] = symbol;

	return symbol;
}



/* ------------------------------------------------------------
 * Returns the variable symbol identified by lexeme or NULL if not
 * found
 * ------------------------------------------------------------
 */
struct symbol_t* symtab_variable_lookup(char* lexeme, char *class, int scope)
{
	int key;
	struct symbol_t* symbol;

	/* Make a hash key out of the given lexeme */
	key = makekey(tolower(lexeme), MAX_SYMTAB_HASHKEYS);
	symbol = sym[key];

	/* Lookup the lexeme */
	while (symbol != NULL) {
		if ((strcmp(tolower(lexeme), tolower(symbol->lexeme)) == 0) &&
				(strcmp(tolower(class), tolower(symbol->class)) == 0) &&
				scope == symbol->scope &&
				symbol->type == SYMTAB_VARIABLE) {
			return symbol;
		} else {
			symbol = symbol->next;
		}
	}

	/* It could be the case that this variable is a global class
	   variable. Must look in the class, and if that fails keep looking
	   into base classes. */
	{
		struct usrdef_t *u;
		struct usrdef_t *new_u;
		int class_scope;
		char *class_name;
		int should_stop_searching = 0;

		u = usrdef_lookup(class);
		class_scope = u->scope;
		class_name = u->name;

		while (should_stop_searching == 0) {

			symbol = sym[key];
			while (symbol != NULL) {
				if ((strcmp(tolower(lexeme), tolower(symbol->lexeme)) == 0) &&
						(strcmp(tolower(class_name), tolower(symbol->class)) == 0) &&
						class_scope == symbol->scope &&
						symbol->type == SYMTAB_VARIABLE)
					return symbol;
				else
					symbol = symbol->next;
			}

			/* Should look next into the base class, if the class extended
			   some other class. */
			if (u->td->data.cl->ci->extend == NULL)
				should_stop_searching = 1;
			else {
				new_u = usrdef_lookup(u->td->data.cl->ci->extend);
				if (new_u == NULL) {
					/* This class was trying to extend a class that does not
					   exist. */
					return NULL;
				}
				u = new_u;
				class_scope = u->scope;
				class_name = u->name;
			}
		}
	}

	return symbol;
}



/* ------------------------------------------------------------
 * Returns 1 if this_class extends base_class, 0 if it doesn't, and -1
 * if the two are not classes to begin with.
 * ------------------------------------------------------------
 */
int symtab_class_extends(char *this_class, char *base_class)
{
	struct usrdef_t *u;
	struct usrdef_t *u_this;
	struct usrdef_t *u_base;
	char *class_name;

	u_this = usrdef_lookup(this_class);
	u_base = usrdef_lookup(base_class);

	if (u_this == NULL ||
			u_base == NULL ||
			u_this->td->type != TYPE_DENOTER_T_CLASS_TYPE ||
			u_base->td->type != TYPE_DENOTER_T_CLASS_TYPE)
		return -1;

	class_name = this_class;
	while(class_name != NULL) {
		struct usrdef_t *new_u;

		u = usrdef_lookup(class_name);
		if (u->td->data.cl->ci->extend == NULL)
			return 0;
		else {
			new_u = usrdef_lookup(u->td->data.cl->ci->extend);
			if (new_u == NULL) {
				/* This class was trying to extend a class that does not
				   exist. */
				return 0;
			}
			class_name = new_u->name;
			if (strcmp(tolower(class_name), tolower(base_class)) == 0)
				return 1;
		}
	}

	return 0;
}



/* ------------------------------------------------------------
 * Returns the location of the function symbol identified by fh in
 * the requested symtab or NULL if not found
 * ------------------------------------------------------------
 */
struct symbol_t* symtab_function_lookup(struct function_heading_t *fh, char *class)
{
	int key;
	struct symbol_t* symbol;

	/* Make a hash key out of the given lexeme */
	key = makekey(tolower(fh->id), MAX_SYMTAB_HASHKEYS);
	symbol = sym[key];

	/* Lookup the lexeme */
	while (symbol != NULL) {
		if ((strcmp(tolower(fh->id), tolower(symbol->lexeme)) == 0) &&
				strcmp(tolower(class), tolower(symbol->class)) == 0 &&
				symbol->type == SYMTAB_FUNCTION &&
				compare_function_params(fh, symbol->data.fd->fd->fh))
			return symbol;
		else
			symbol = symbol->next;
	}

	return symbol;
}



/* ------------------------------------------------------------
 * Returns 1 if the parameter lists of a and b match, 0 otherwise
 * ------------------------------------------------------------
 */
int compare_function_params(struct function_heading_t *a, struct function_heading_t *b)
{
	struct formal_parameter_section_list_t *tempa, *tempb;
	struct identifier_list_t *ila, *ilb;

	tempa = a->fpsl;
	tempb = b->fpsl;

	if (tempa != NULL) ila = tempa->fps->il;
	if (tempb != NULL) ilb = tempb->fps->il;

	while ((tempa != NULL) && (tempb != NULL)) {
		if ((tempa->fps->is_var == tempb->fps->is_var) &&
				(strcmp(tolower(tempa->fps->id), tolower(tempb->fps->id)) == 0)) {
			ila = ila->next;
			ilb = ilb->next;
			if (ila == NULL) {
				tempa = tempa->next;
				if (tempa != NULL) ila = tempa->fps->il;
			}
			if (ilb == NULL) {
				tempb = tempb->next;
				if (tempb != NULL) ilb = tempb->fps->il;
			}
		}
		else
			return(0);
	}
	if ((tempa == NULL) && (tempb == NULL))
		return(1);
	else
		return(0);
}




/* ------------------------------------------------------------
 * Returns the location of the function symbol that matches fd in
 * the requested symtab or NULL if not found
 * ------------------------------------------------------------
 */
struct symbol_t* symtab_function_desig_lookup(struct function_designator_t *fd, char *class)
{
	int key;
	struct symbol_t* symbol;

	/* Make a hash key out of the given lexeme */
	key = makekey(tolower(fd->id), MAX_SYMTAB_HASHKEYS);
	symbol = sym[key];

	/* Lookup the lexeme */
	while (symbol != NULL) {
		if ((strcmp(tolower(fd->id), tolower(symbol->lexeme)) == 0) &&
				strcmp(tolower(class), tolower(symbol->class)) == 0 &&
				(symbol->type == SYMTAB_FUNCTION) &&
				compare_function_desig_params(fd, symbol->data.fd->fd->fh))
			return symbol;
		else
			symbol = symbol->next;
	}

	/* It could be the case that this function is present in a base
	   class. */
	{
		struct usrdef_t *u;
		struct usrdef_t *new_u;
		char *class_name;
		int should_stop_searching = 0;

		u = usrdef_lookup(class);
		class_name = u->td->data.cl->ci->extend;

		while (should_stop_searching == 0) {

			symbol = sym[key];
			while (symbol != NULL) {
				if (strcmp(tolower(fd->id), tolower(symbol->lexeme)) == 0 &&
						strcmp(tolower(class_name), tolower(symbol->class)) == 0 &&
						symbol->type == SYMTAB_FUNCTION)
					return symbol;
				else
					symbol = symbol->next;
			}

			if (u->td->data.cl->ci->extend == NULL)
				should_stop_searching = 1;
			else {
				new_u = usrdef_lookup(u->td->data.cl->ci->extend);
				if (new_u == NULL) {
					/* This class was trying to extend a class that does not
					   exist. */
					return NULL;
				}
				u = new_u;
				class_name = u->name;
			}
		}
	}

	return symbol;
}



/* ------------------------------------------------------------
 * Returns 1 if the parameter lists of a and b match, 0 otherwise
 * ------------------------------------------------------------
 */
int compare_function_desig_params(struct function_designator_t *a, struct function_heading_t *b)
{
	struct actual_parameter_list_t *tempa;
	struct formal_parameter_section_list_t *tempb;
	struct identifier_list_t *ilb;

	tempa = a->apl;
	tempb = b->fpsl;
	if (tempa == NULL && tempb == NULL) return (1);
	else if ((tempa != NULL && tempb == NULL) || (tempa == NULL && tempb != NULL)) return (0); 
	ilb = tempb->fps->il;

	while ((tempa != NULL) && (tempb != NULL)) {
		if (strcmp(tolower(tempa->ap->e1->expr->type), tolower(tempb->fps->id)) == 0) {
			ilb = ilb->next;
			if (ilb == NULL) {
				tempb = tempb->next;
				if (tempb != NULL) ilb = tempb->fps->il;
			}
			tempa = tempa->next;
		}
		else
			return(0);
	}
	if ((tempa == NULL) && (tempb == NULL))
		return(1);
	else
		return(0);
}



/* ------------------------------------------------------------
 * Initializes the symbol table
 * ------------------------------------------------------------
 */
void symtab_init()
{
	int i;

	for (i = 0; i < MAX_SYMTAB_HASHKEYS; i++)
		sym[i] = NULL;
}



/* ------------------------------------------------------------
 * Prints the contents of the symbol table
 * ------------------------------------------------------------
 */
void symtab_print(int numOfTabs)
{
	struct symbol_t *temp;
	int i;

	for (i = 0; i < MAX_SYMTAB_HASHKEYS; i++) {
		temp = sym[i];

		while(temp != NULL){

			print_tabs(numOfTabs);
			printf("Symbol: %s\n", temp->lexeme);
			print_tabs(numOfTabs);
			printf("Line: %d\n", temp->line_number);
			print_tabs(numOfTabs);
			printf("Scope: %d\n", temp->scope);
			print_tabs(numOfTabs);
			printf("Class: %s\n", temp->class);
			print_tabs(numOfTabs);
			printf("Func: %s\n", temp->func);
			print_tabs(numOfTabs);
			if (temp->type == SYMTAB_VARIABLE) {
				printf("SymbolType: variable\n");
				print_tabs(numOfTabs);
				printf("BaseType: %s\n", temp->data.type_name);
			} else if (temp->type == SYMTAB_FUNCTION) {
				printf("SymbolType: function\n");
				symtab_print_function_declaration(temp->data.fd->fd, numOfTabs+1);
			} else if (temp->type == SYMTAB_CLASS) {
				printf("SymbolType: class\n");
				print_tabs(numOfTabs);
				printf("BaseType: %s\n", temp->data.type_name);
			}
			print_tabs(numOfTabs);
			printf("Offset: %ld\n", temp->offset);
			printf("--------------------------\n");

			temp = temp->next;
		}
	}

}



/* ------------------------------------------------------------
 * Prints all information related to a function_declaration
 * ------------------------------------------------------------
 */
void symtab_print_function_declaration(struct function_declaration_t *fd,
		int numOfTabs)
{
	struct formal_parameter_section_list_t *temp;
	int param_count = 0;

	print_tabs(numOfTabs);
	printf("Result type: %s\n", fd->fh->res);
	print_tabs(numOfTabs);
	printf("Formal parameters:\n");

	temp = fd->fh->fpsl;
	while (temp != NULL) {
		struct identifier_list_t *il;
		int id_count = 0;

		print_tabs(numOfTabs+1);
		printf("Parameter(s) %d is ", param_count);

		if (temp->fps->is_var)
			printf("variable(s) ");
		else
			printf("constant(s) ");

		printf("of type: %s\n", temp->fps->id);
		il = temp->fps->il;
		while (il != NULL) {
			print_tabs(numOfTabs+2);
			printf("#%d: %s\n", id_count, il->id);

			id_count++;
			il = il->next;
		}

		param_count++;
		temp = temp->next;
	}

}



/* Iterates over the symbol table and computes the offsets of all
   variables for all classes.

NOTE: The implementation of this function would have been
embarrassingly simple and efficient had we originally designed a
symbol table per function, nested in a symbol table per class,
instead of a flat symbol table. Variable and function lookups would
have been equally trivial.

Instead the implementation is terribly inefficient.
*/

struct symbol_t * symtab_lookup(char * symbol, char * class, char * func, int type)
{
	struct symbol_t *temp;
	int i;
	for (i = 0; i < MAX_SYMTAB_HASHKEYS; i++) 
	{      
		temp = sym[i];

		while(temp != NULL)
		{
			if (temp->type == type)
			{
				if (!strcmp(tolower(symbol), tolower(temp->lexeme)) && !strcmp(tolower(class), tolower(temp->class)))
				{
					if (func != NULL)
					{
						if (temp->func != NULL && !strcmp(tolower(func), tolower(temp->func)))
							return temp;
					}
					else
						return temp;
				}
			}
			temp = temp->next;
		}
	}
	return NULL;
}


int compute_func_offsets (struct function_declaration_t *fd, char *class, int scope)
{
	int totSize = MIPS_FUNC_GLOBALS_SIZE;
	struct variable_declaration_list_t * temp = fd->fb->vdl; 
	struct symbol_t* tempSym;
	struct formal_parameter_section_list_t * tempFpsl = fd->fh->fpsl; 
	struct usrdef_t * utemp;		
    /* function params */
	while (tempFpsl != NULL)
	{
		int vSizeParam=0;
		struct identifier_list_t *tempFpsidl = tempFpsl->fps->il;
		while (tempFpsidl!=NULL)
		{
			tempSym = symtab_variable_lookup(tempFpsidl->id, class, scope);
	        utemp = usrdef_lookup(tempSym->data.type_name);
            if (utemp)
                vSizeParam = utemp->size; 

			//printf("var is %s offset is %d \n", tempSym->lexeme, totSize);
			tempSym->offset = totSize;
			totSize = totSize + vSizeParam; 
			tempFpsidl = tempFpsidl->next;
		}
		tempFpsl = tempFpsl->next;
	}

	while (temp != NULL)
	{
		int vSize = compute_symbol_size(temp->vd->tden);
		struct identifier_list_t *tempidl = temp->vd->il;
		while (tempidl!=NULL)
		{
			tempSym = symtab_variable_lookup(tempidl->id, class, scope);
			//printf("var is %s offset is %d \n", tempSym->lexeme, totSize);
			tempSym->offset = totSize;
			totSize = totSize + vSize; 
			tempidl = tempidl->next;
		}
		temp = temp->next;
	}

    /* Check if return variable exists and add offset for that */
    tempSym = symtab_lookup (fd->fh->id, class, fd->fh->id, SYMTAB_VARIABLE);
    if (tempSym)
        {
        tempSym->offset = totSize;
        totSize = totSize + gen_primitive_size("integer");
        }
	return totSize;
}

int symtab_get_func_size (char * func)		
	    {		
	    struct symbol_t *temp;		
	  struct usrdef_t * utemp;		
	    int i;		
	    int size = MIPS_FUNC_GLOBALS_SIZE;		
	    for (i = 0; i < MAX_SYMTAB_HASHKEYS; i++)		
	        {		
	        temp = sym[i];		
			
	        while(temp != NULL)		
	            {		
	            if (temp->type != SYMTAB_FUNCTION)		
	                {		
	                if (temp->func != NULL && !strcmp(func, temp->func))		
	                    {		
	                    utemp = usrdef_lookup(temp->data.type_name);		
	                    size = size + utemp->size;		
	                    }		
	                }		
	            temp = temp->next;		
	            }		
	        }		
	    return size;		
			
	    }

void symtab_compute_offset_scope(int scope, int baseoffset, char * class)
{
	struct symbol_t *temp;
	int i, itemp;
	int offset = baseoffset; 
	struct usrdef_t * utemp;
	int nonEmpty = 0;
	char curClass[256];

	if (class)
		strcpy(curClass, class);

	for (i = 0; i < MAX_SYMTAB_HASHKEYS; i++) 
	{      
		temp = sym[i];

		while(temp != NULL)
		{
			if (temp->scope == scope)
			{
				nonEmpty = 1;
				if (temp->type == SYMTAB_VARIABLE)
				{
					if (!(temp->func))
					{
						if (strcmp(curClass, temp->class))
						{
							// printf("classes not same |%s| |%s| \n", curClass, temp->class);
							offset = 0;
							// printf("var is %s offset is %d \n", temp->lexeme, offset);
							memset(curClass, 0, sizeof(curClass));
							strcpy (curClass, temp->class);
						}
						else
						{
							// printf("var is %s offset is %d \n", temp->lexeme, offset);
						}
						temp->offset = offset;
						utemp = usrdef_lookup(temp->data.type_name);

						if (utemp)
						{
							//   printf("wtf utemp for %s is %d \t", 
							//          temp->data.type_name, utemp->size);
                            if (utemp->td->type == TYPE_DENOTER_T_CLASS_TYPE)
                                offset = offset + gen_primitive_size(PRIMITIVE_TYPE_NAME_POINTER);
                            else
							    offset = offset + utemp->size;
						}

						// printf("DONE var is %s offset is %d \n", temp->lexeme, offset);
					}
				}
				if (temp->type == SYMTAB_FUNCTION)
				{

					if (strcmp(curClass, temp->class))
					{
						offset = 0;
						memset(curClass, 0, sizeof(curClass));
						strcpy (curClass, temp->class);
					}
					//temp->offset = offset;
					itemp = compute_func_offsets(temp->data.fd->fd, curClass, temp->scope);
					//printf("itemp is %d and offset is %d func is %s\n", itemp, offset, 
					//       temp->data.fd->fd->fh->id);
					//offset = offset + itemp; 
				}
			}
			temp = temp->next;
		}
	}

	if (nonEmpty)
	{
		symtab_compute_offset_scope(scope+1, offset, curClass);
	}
}

void symtab_compute_offsets()
{
	symtab_compute_offset_scope(0, 0, NULL);
}


/* Determines whether the requested symbol is a variable that is not
   declared locally */
int symtab_variable_global(struct symbol_t *s, int scope)
{
	if (s->scope == scope)
		return 0;
	else
		return 1;
}
